Let us start with a simple synchronous multi–agent system. Thanks to the synchronicity, there is not a global mechanism, which prevents several automatons from performing an action at exactly the same time by executing a synchronised statement, see Synchronisation for details.
Let there be three trains which share a common track in a narrow tunnel
at a certain part of their otherwise disjoint routes.
We need a signalling system which prevents two or more trains from entering the common segment at the same time.
The signalling is provided by a special automaton Environment
. There is only a single
environment in a system of agents, and the environment differs from an agent automaton in that
its state can be made visible anywhere.
Let us declare, that we define an
// // Test "Faulty train protocol" for any number of trains. // class pomdp sync;An asynchronous equivalent, i.e. an
sync
keyword.
The Verics' assembly language is mostly related 1:1 to the automata generated, and thus, in order to vary the number of trains at the change of a constant value, we need a preprocessor. The preprocessor is simply an interpreter of the same Verics language used elsewhere within the Verics environment.
Let us declare the said constant:
// number of trains common Z NUM := 3;A constant like that, if precedes all preprocessor directives, is visible by both the preprocessor and the automatons.
Let the environment feature a lock (a railway signal, a semaphore), toggled to red if one of the trains arrives at the common track. If the train leaves the track, the semaphore is switched back to green.
environment { Z{GREEN, RED} lights := GREEN; #for Z i~ := 1; i <= NUM - 1; ++i { [out${i}] lights == RED -> lights := GREEN; [in${i}] lights == GREEN -> lights := RED; #} };Let's look at the same definition, yet expanded by the preprocessor:
environment { Z{GREEN, RED} lights := GREEN; [out1] lights == RED -> lights := GREEN; [in1] lights == GREEN -> lights := RED; [out2] lights == RED -> lights := GREEN; [in2] lights == GREEN -> lights := RED; };The identifiers in square brackets define labels, here the
sync
, they would be synchronisation labels). Two automatons may
synchronise, if they perform the same action. Then we have boolean equations,
which
We see, that the environment can perform two actions environment.in1
and
environment.in2
, that allow the trains 1 and 2 to enter. Yet, we defined
NUM := 3
, and thus, what about the third train? It is faulty, and ignores
the environment's actions completely, what translates to its ignorance about the
tunnel's semaphore.
Let the definiton of all trains be as follows:
#for Z i~ := 1; i <= NUM; ++i {
agent train${i} {
Z{WAIT, TUNNEL, AWAY} phase := AWAY;
[] true -> true;
[approach${i}] phase == AWAY -> phase := WAIT;
// the train NUM
is faulty
[${"in" + i}] phase == WAIT -> phase := TUNNEL;
[${"out" + i}] phase == TUNNEL -> phase := AWAY;
};
def in_tunnel${i} := train${i}.phase == TUNNEL;
#}
We see, that all trains are the same including the faulty one. The difference is,
that the faulty train's
actions train3.in3
and train3.out3
do not have their counterparts
in the environments, i.e. there are no actions environment.in3
and
environment.out3
.
The statement beginning with def
is a formula, which will allow
a more terse property definition later.
Let us model–check the model. The engineer at the train 1 would want to be always assured, that no crash is possible:
property CTL*K test_train1 := !AG(K(train1, #B first~ := T; #for Z a~ := 1; a <= NUM; ++a { # for Z b~ := 1; b <= NUM; ++b { # if a < b { ${first ? "" else "&&"} (!in_tunnel${a} || !in_tunnel${b}) # first := F; # } # } # } ));which is expanded by the preprocessor into
property CTL*K test_train1 := !A G(K(train1, (!in_tunnel1 || !in_tunnel2) && (!in_tunnel1 || !in_tunnel3) && (!in_tunnel2 || !in_tunnel3) ));
Files:
Command line:
veric -L -oi FTC.vxs